/**
 * PaperResultService.java
 * Version 1.0.0
 * Created on 2018年7月18日
 */
package com.yincheng.oes.business.exam.judgepaper.service;

import com.yincheng.oes.attachment.entity.Attachment;
import com.yincheng.oes.attachment.mapper.AttachmentMapper;
import com.yincheng.oes.business.base.entity.QuestionKnowledgeOutlineExt;
import com.yincheng.oes.business.base.mapper.QuestionKnowledgeOutlineMapper;
import com.yincheng.oes.business.config.PaperResultStatus;
import com.yincheng.oes.business.consist.ResultCodeEnum;
import com.yincheng.oes.business.exam.base.entity.Paper;
import com.yincheng.oes.business.exam.base.mapper.PaperMapper;
import com.yincheng.oes.business.exam.export.model.ExportPaperParam;
import com.yincheng.oes.business.exam.judgepaper.entity.*;
import com.yincheng.oes.business.exam.judgepaper.mapper.ExamStudentMapper;
import com.yincheng.oes.business.exam.judgepaper.mapper.PaperResultDetailMapper;
import com.yincheng.oes.business.exam.judgepaper.mapper.PaperResultMapper;
import com.yincheng.oes.business.model.result.BusinessResult;
import com.yincheng.oes.business.util.AttachmentUtils;
import com.yincheng.oes.business.util.ExportFileUtils;
import com.yincheng.oes.business.util.ExportWord;
import com.yincheng.oes.business.util.TimeUtils;
import com.yincheng.oes.sso.entity.User;
import com.yincheng.oes.sso.util.UserUtils;
import freemarker.template.TemplateException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @author yangsy
 * @version 1.0.0
 * @since 创建时间：2018年7月18日
 * 功能描述：学生考试成绩服务类
 * 邮箱地址：446948434@qq.com
 */
@Service
public class PaperResultService {
    /**
     * 日志
     */
    private static final Logger logger = LogManager.getLogger(PaperResultService.class);

    /**
     * doc文件名后缀
     */
    private static final String DOC_SUFFIX = ".doc";

    @Autowired
    private PaperResultMapper paperResultMapper;

    @Autowired
    private PaperResultDetailMapper paperResultDetailMapper;

    @Autowired
    private ExamStudentMapper examStudentMapper;

    @Autowired
    private PaperMapper paperMapper;

    @Autowired
    private QuestionKnowledgeOutlineMapper questionKnowledgeOutlineMapper;

    @Autowired
    private AttachmentMapper attachmentMapper;

    /**
     * 线程池，用来执行导出程序
     */
    private final static ExecutorService singleExecutor = Executors.newSingleThreadExecutor();

    /**
     * 保存考试成绩
     *
     * @param bean PaperResult
     */
    @Transactional
    public void savePaperResult(PaperResult bean) {
        List<PaperResultQues> resultDetails = bean.getPaperResultDetails();

        paperResultDetailMapper.deleteByResultId(bean.getId());
        Float score = (float) 0;
        for (PaperResultQues e : resultDetails) {
            if (e.getAnswer().equals(e.getMyAnswer())) {
                e.setCorrectFlag("1");
                score = score + e.getStandardScore();
            } else {
                e.setCorrectFlag("0");
            }
            e.setCreateTime(new Date());
            e.setPaperResultId(bean.getId());
            paperResultDetailMapper.insert(e);
        }
        bean.setScore(score);
        bean.setEndTime(new Date());
        int i = paperResultMapper.update(bean);
        if (i == 0) {
            if (bean.getUserId() == null)
                bean.setUserId(UserUtils.getUser().getUserId());
            paperResultMapper.insert(bean);

        }
    }

    /**
     * 获取答卷基本信息
     *
     * @param resultId
     * @return
     */
    public PaperStudentResult getPaperResultBaseInfo(String resultId) {
        PaperStudentResult paperResult = paperResultMapper.selectByResultId(resultId);
        if (paperResult == null) {
            throw new IllegalArgumentException("此答卷尚未保存或数据异常");
        }
        return paperResult;
    }

    /**
     * 根据考试成绩id获取学生答卷信息
     *
     * @param resultId
     * @return
     */
    public PaperStudentResult getPaperResult(String resultId) {
        PaperStudentResult paperResult = paperResultMapper.selectByResultId(resultId);
        if (paperResult == null) {
            throw new IllegalArgumentException("此答卷尚未保存或数据异常");
        }

        List<PaperResultQues> list = null;
        Integer tatal = paperResultDetailMapper.countByResultId(resultId);
        if (tatal > 0) {
            list = paperResultDetailMapper.selectDetailsByResultId(resultId);
        } else {
            list = paperResultDetailMapper.selectDetailsByPaperId(paperResult.getPaperId());
        }

        Float totalScore = Float.valueOf(0);
        Float standardScore = Float.valueOf(0);
        List<QuestionKnowledgeOutlineExt> quesOutlines = questionKnowledgeOutlineMapper.selectByResultId(resultId);
        for (PaperResultQues paperResultQues : list) {
            Integer quesId = paperResultQues.getQuesId();
            List<String> contents = new ArrayList<String>();
            for (QuestionKnowledgeOutlineExt knowledgeOutline : quesOutlines) {
                if (knowledgeOutline.getQuesId().equals(quesId)) {
                    String outlineContext = knowledgeOutline.getContents();
                    contents.add(outlineContext);
                } else
                    continue;
            }
            paperResultQues.setOutlineTexts(contents);

            totalScore = totalScore + paperResultQues.getScore();
            standardScore = standardScore + paperResultQues.getStandardScore();
        }
        paperResult.setPaperResultDetails(list);
        paperResult.setScore(totalScore);
        paperResult.setStandardScore(standardScore);
        paperResult.setStartTime(paperResult.getStartTime() == null ? new Date() : paperResult.getStartTime());
        return paperResult;
    }

    /**
     * 根据试卷id获取当前用户的试卷详细信息
     *
     * @param paperId
     * @return
     */
    public PaperStudentResult getPaperResultByPaperId(String paperId) {

        // 获取试卷再获取题目列表
        String userId = UserUtils.getUser().getUserId();
        PaperStudentResult studentResult = paperResultMapper.selectByPaperId(paperId, userId);
        List<PaperResultQues> list = null;
        if (studentResult.getId() != null) {
            list = paperResultDetailMapper.selectDetailsByResultId(studentResult.getId());
        } else {
            list = paperResultDetailMapper.selectDetailsByPaperId(paperId);
        }
        Float totalScore = Float.valueOf(0);
        Float standardScore = Float.valueOf(0);
        List<QuestionKnowledgeOutlineExt> quesOutlines = questionKnowledgeOutlineMapper.selectByPaperId(paperId);
        for (PaperResultQues paperResultQues : list) {
            Integer quesId = paperResultQues.getQuesId();
            List<String> contents = new ArrayList<String>();
            for (QuestionKnowledgeOutlineExt knowledgeOutline : quesOutlines) {
                if (knowledgeOutline.getQuesId().equals(quesId)) {
                    String outlineContext = knowledgeOutline.getContents();
                    contents.add(outlineContext);
                } else
                    continue;
            }
            paperResultQues.setOutlineTexts(contents);

            totalScore = totalScore + paperResultQues.getScore();
            standardScore = standardScore + paperResultQues.getStandardScore();
        }
        studentResult.setPaperResultDetails(list);
        studentResult.setScore(totalScore);
        studentResult.setStandardScore(standardScore);
        studentResult.setStartTime(studentResult.getStartTime() == null ? new Date() : studentResult.getStartTime());
        studentResult.setId(studentResult.getId() == null ? UUID.randomUUID().toString() : studentResult.getId());
        return studentResult;
    }

    /**
     * 初始化答卷实体信息
     *
     * @param resultId
     * @param paperId
     * @return
     */
    private PaperResult createNewPaperResult(String resultId, String paperId) {
        PaperStudentResult paperResult = new PaperStudentResult();
        paperResult.setId(resultId);
        paperResult.setStartTime(new Date());
        paperResult.setPaperId(paperId);
        paperResult.setStatus(PaperResultStatus.DURING_ANSWER.getStatus());// 答题中
        return paperResult;
    }

    /**
     * 获取未答卷的学生列表
     *
     * @param paperId
     * @return
     */
    public List<User> getUnAnsweredStudent(String paperId) {
        List<User> list = examStudentMapper.selectUnAnsweredUsers(paperId);
        return list;
    }

    /**
     * 保存学生和试卷信息
     *
     * @param resultId
     * @param paperId
     * @param userId
     */
    public void saveUserOfPaper(String resultId, String paperId, String userId) {
        // 判断该学生是否已经答题
        int i = examStudentMapper.judgeUserAnswer(resultId, userId);
        if (i == 0) {
            PaperResult paperResult = createNewPaperResult(resultId, paperId);
            int update = paperResultMapper.update(paperResult);
            if (update == 0) {
                paperResultMapper.insert(paperResult);
            }
        } else {
            throw new IllegalArgumentException("该学生已经在答题中，请重新选择");
        }
    }

    /**
     * 统计试卷答题分布情况
     *
     * @param resultId
     * @return
     */
    public List<QuesErrorStatics> staticsErrorExam(String resultId) {
        List<QuesErrorStatics> list = paperResultDetailMapper.staticsErrorQuesByResultId(resultId);
        return list;
    }

    /**
     * 根据考卷id统计错题的知识点
     *
     * @param resultId
     * @return
     */
    public List<ErrorOutlineExport> staticsErrorOutline(String resultId) {
        List<ErrorOutlineExport> list = paperResultDetailMapper.staticsErrorOutlineByResultId(resultId);
        return list;
    }

    /**
     * 根据考卷id导出学生答题分析报告
     *
     * @param resultId
     * @param os
     * @throws IOException
     * @throws TemplateException
     */
    public void exportStudentAnswerWord(String resultId, OutputStream os) throws IOException, TemplateException {
        paperResultMapper.changeExportStatus(resultId);
        PaperStudentResult studentResult = getPaperResult(resultId);
        List<ErrorOutlineExport> errorOutline = staticsErrorOutline(resultId);
        studentResult.setErrorOutlineExports(errorOutline);
        buildExportWord(os, studentResult);
    }

    public BusinessResult exportZipStudentAnswers(ExportPaperParam param){
        logger.debug("start batchExportStudentAnswers sync, param is {}", param);
        if (Objects.isNull(param) || StringUtils.isEmpty(param.getPaperId())) {
            logger.error("batchExportStudentAnswers param or paperId is null");
            return new BusinessResult(ResultCodeEnum.NO_QUERY_PARAM);
        }
        String userId = UserUtils.getUser().getUserId();
        singleExecutor.execute(()->{
            try {
                long start = System.currentTimeMillis();
                exportZipStudentAnswersSync(param,userId);
                logger.debug("batchExportStudentAnswers cost time is {}ms", System.currentTimeMillis()-start);
            } catch (IOException | TemplateException e) {
                logger.error("batchExportStudentAnswers occurred error ", e);
            }
        });
        return new BusinessResult(ResultCodeEnum.SUCCESS);
    }


    public void exportZipStudentAnswersSync(ExportPaperParam param, String userId) throws IOException, TemplateException {
        if(Objects.isNull(param)||StringUtils.isEmpty(param.getPaperId())){
            logger.error("batchExportStudentAnswers param or paperId is null");
            return;
        }
        final String paperId = param.getPaperId();
        Paper paper = paperMapper.selectById(paperId);
        if (Objects.isNull(paper)) {
            logger.error("batchExportStudentAnswers paper is null, paperId is {}", paperId);
            return ;
        }
        String paperName = paper.getPaperName();

        // 1.获取所有已完成的答卷
        List<PaperStudentResult> paperStudentResults = paperResultMapper.selectPaperStudentResultsByParam(param);
        if (CollectionUtils.isEmpty(paperStudentResults)) {
            logger.warn("batchExportStudentAnswers paperStudentResults are null, paperId is {}", paperId);
            return;
        }
        // 2.获取答卷的所有题目列表
        List<PaperResultQues> paperResultQuesList = paperResultDetailMapper.selectResultDetailsByParam(param);
        if (CollectionUtils.isEmpty(paperResultQuesList)) {
            logger.error("batchExportStudentAnswers paperResultQuesList are null, paperId is {}", paperId);
            return;
        }
        // 按照答卷id进行分组
        Map<String, List<PaperResultQues>> paperResultQuesMap = paperResultQuesList.stream()
                .collect(Collectors.groupingBy(PaperResultQues::getPaperResultId));

        // 3.获取答卷错题关联的知识点集合
        List<ErrorOutlineExport> errorOutlineExportList = paperResultDetailMapper.staticsErrorOutlineByParam(param);
        Map<String, List<ErrorOutlineExport>> errorOutlineResultMap = new HashMap<>();

        // 4.获取题目关联的大纲信息
        List<QuestionKnowledgeOutlineExt> quesOutlines = questionKnowledgeOutlineMapper.selectByPaperId(paperId);
        Map<Integer, List<String>> quesOutlinesMap = quesOutlines.stream()
                .collect(Collectors.groupingBy(QuestionKnowledgeOutlineExt::getQuesId,
                        Collectors.mapping(QuestionKnowledgeOutlineExt::getContents, Collectors.toList())));
        if (CollectionUtils.isNotEmpty(errorOutlineExportList)) {
            // 按照答卷id分组
            Map<String, List<ErrorOutlineExport>> errorOutlineExportMap = errorOutlineExportList.stream()
                    .collect(Collectors.groupingBy(ErrorOutlineExport::getResultId));
            errorOutlineExportMap.forEach((resultId, errorOutlines) -> {
                // 按照知识点id分组
                errorOutlines.stream().collect(Collectors.groupingBy(ErrorOutlineExport::getOutlineId))
                        .forEach((outlineId, errorOutlineGrouped) -> {
                            if (CollectionUtils.isNotEmpty(errorOutlineGrouped)) {

                                // 将分组后的错题知识点的quesOrder聚合
                                String joinedQuesOrder = errorOutlineGrouped.stream()
                                        .map(ErrorOutlineExport::getQuesOrder).collect(Collectors.joining(","));
                                ErrorOutlineExport errorOutlineExport = errorOutlineGrouped.get(0);
                                errorOutlineExport.setQuesOrder(joinedQuesOrder);
                                errorOutlineResultMap.computeIfAbsent(resultId, v -> new ArrayList<>())
                                        .add(errorOutlineExport);
                            }
                        });
            });
        }
        List<PaperStudentResult> readyExportPapers = paperStudentResults.stream().peek(paperResult -> {
            List<PaperResultQues> paperResultQuesDetails = paperResultQuesMap
                    .computeIfAbsent(paperResult.getId(), k -> new ArrayList<>());
            // 设置题目的关联知识点列表
            paperResultQuesDetails.forEach(paperResultQuesDetail -> paperResultQuesDetail
                    .setOutlineTexts(quesOutlinesMap.get(paperResultQuesDetail.getQuesId())));
            // 获取试卷总得分
            Float totalScore = paperResultQuesDetails.stream().map(PaperResultQues::getScore).reduce(Float::sum)
                    .get();
            // 获取试卷满分
            Float totalStandScore = paperResultQuesDetails.stream().map(PaperResultQues::getStandardScore)
                    .reduce(Float::sum).get();
            paperResult.setPaperResultDetails(paperResultQuesDetails);
            paperResult.setScore(totalScore);
            paperResult.setStandardScore(totalStandScore);
            List<ErrorOutlineExport> errorOutlineExports = errorOutlineResultMap
                    .computeIfAbsent(paperResult.getId(), k -> new ArrayList<>());
            paperResult.setErrorOutlineExports(errorOutlineExports);
        }).collect(Collectors.toList());


        String fileName = paperName + "_批量导出成绩_" + TimeUtils.getYyMMddHHmm() + ".zip";
        // 生成zip文件
        File zipFile = getFile(fileName, readyExportPapers);
        // 保存临时zip文档
        Attachment attachment =  AttachmentUtils.getAttachment(fileName,zipFile.length(),".zip",userId);
        attachmentMapper.insert(attachment);
    }

    private File getFile(String fileName, List<PaperStudentResult> paperStudentResults) throws IOException {
        logger.debug("start create student result paper zip file, total doc size is {}", CollectionUtils.size(paperStudentResults));
        File zipFile = FileUtils.getFile(ExportFileUtils.getTmpBaseFile(), fileName);
        try (ZipArchiveOutputStream zaos = new ZipArchiveOutputStream(zipFile)) {
            paperStudentResults.forEach(paperResult -> {
                try {
                    logger.debug("start create student result paper doc, paperResultId is {}", paperResult.getId());
                    String tempFileName = UUID.randomUUID()+ DOC_SUFFIX;
                    File myWord =  FileUtils.getFile(ExportFileUtils.getTmpBaseFile(), tempFileName);
                    FileOutputStream fos = new FileOutputStream(myWord);
                    buildExportWord(fos, paperResult);
                    String docName = getPerDocName(paperResult);
                    ZipArchiveEntry archiveEntry = new ZipArchiveEntry(docName);
                    archiveEntry.setSize(myWord.length());
                    zaos.putArchiveEntry(archiveEntry);
                    try {
                        FileUtils.copyFile(myWord, zaos);
                    } finally {
                        zaos.closeArchiveEntry();
                        myWord.deleteOnExit();
                    }
                } catch (IOException | TemplateException e) {
                    logger.error("create per student result paper doc occurred error ", e);
                }
            });
        }
        return zipFile;
    }

    private String getPerDocName(PaperStudentResult paperResult) {
        StringBuilder sb = new StringBuilder();
        if(StringUtils.isEmpty(paperResult.getAssistantName())){
            sb.append("助教空");
        }else {
            sb.append(paperResult.getAssistantName());
        }
        sb.append("--");
        sb.append(paperResult.getUserName());
        if(StringUtils.isNotEmpty(paperResult.getAssistantName())){
            sb.append("--");
            sb.append(paperResult.getTelephone());
        }
        sb.append(DOC_SUFFIX);
        return sb.toString();
    }

    /**
     * @param os os
     * @param studentResult studentResult
     * @throws IOException
     * @throws TemplateException
     */
    private void buildExportWord(OutputStream os, PaperStudentResult studentResult) throws IOException, TemplateException {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("paperName", studentResult.getPaperName());
        dataMap.put("userName", studentResult.getUserName());
        dataMap.put("score", studentResult.getScore());
        dataMap.put("totalScore", studentResult.getStandardScore());
        dataMap.put("errorOutlines", studentResult.getErrorOutlineExports());
        List<PaperResultQues> details = studentResult.getPaperResultDetails();
        dataMap.put("details", details);

        String fileName = "分析报告2016.ftl";

        ExportWord.exportWord(dataMap, fileName, os);
    }
}
